// Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>

#include "timer.h"

#if 1
#include "log.h"
#define timer_func_in(fmt, args...)			xfunc_in(fmt, ## args)
#define timer_func_out(fmt, args...)		xfunc_out(fmt, ## args)
#define timer_iprintf(fmt, args...)			xprintf(SDK_INFO, fmt, ## args)
#define timer_dprintf(fmt, args...)			xprintf(SDK_DBG, fmt, ## args)
#define timer_eprintf(fmt, args...)			xprintf(SDK_ERR, fmt, ## args)
#define timer_std_eprintf(fmt, args...)		xprintf(SDK_STD_ERR, fmt, ## args)
#else
#define timer_func_in(fmt, args...)			fprintf(stdout, "+%s " fmt "\n", __FUNCTION__, ## args)
#define timer_func_out(fmt, args...)		fprintf(stdout, "-%s " fmt "\n", __FUNCTION__, ## args)
#define timer_iprintf(fmt, args...)			fprintf(stdout, fmt, ## args)
#define timer_dprintf(fmt, args...)			fprintf(stdout, fmt, ## args)
#define timer_eprintf(fmt, args...)			fprintf(stderr, "%s " fmt, __FUNCTION__, ## args)
#define timer_std_eprintf(fmt, args...)		fprintf(stderr, "%s err=%s(%d):" fmt,\
											__FUNCTION__, strerror(errno), errno, ## args)
#endif

#define CREATED_MASK		0x54494D45UL

#if defined(PTHREAD_TIMER)
//#define TIMER_ASSERT

typedef struct pthread_timer_s {
	unsigned		tt_inited;
	pthread_t 		tt_thread;
	pthread_mutex_t tt_lock;
	pthread_cond_t 	tt_cond;

	struct list_head	tt_head;
	timer_obj_t			*tt_active;
	
} pthread_timer_t;

static pthread_timer_t pthread_timer;

static struct timespec *get_timespec_ms(struct timespec *ts, int timeout_ms)
{
	#define nsec_per_sec	(1000*1000*1000)
	#define ps_timeval2timespec(tv,ts)	\
		((ts)->tv_sec = (tv)->tv_sec, (ts)->tv_nsec = (tv)->tv_usec * 1000)
	struct timeval tv;
	int sec, nsec;

	sec = timeout_ms/1000;
	nsec = (timeout_ms%1000) * 1000/*usec*/ * 1000/*nsec*/;

	gettimeofday(&tv, NULL);
	ps_timeval2timespec(&tv, ts);
	ts->tv_sec += sec;
	ts->tv_nsec += nsec;

	if (ts->tv_nsec >= nsec_per_sec) {
		ts->tv_sec++;
		ts->tv_nsec -= nsec_per_sec;
	}
	return ts;
}

static struct timespec *cmp_earlier_timespec(struct timespec *ts1, struct timespec *ts2)
{
	if (ts1->tv_sec == ts2->tv_sec) {
		if (ts1->tv_nsec < ts2->tv_nsec)
			return ts1;
	}
	else if (ts1->tv_sec < ts2->tv_sec)
		return ts1;
	return ts2;
}

#if defined(TIMER_ASSERT)
static void time_assert(struct timespec *ts)
{
	struct timespec curr_ts;

	get_timespec_ms(&curr_ts, 0);

	assert(cmp_earlier_timespec(&curr_ts, ts) == ts);
}
#endif

static void *timer_thread(void *thr_data)
{
	pthread_timer_t *ptt = &pthread_timer;
	struct list_head *head = &ptt->tt_head;
	timer_obj_t *to;
	void *data;
	void (*callback)(void *data);
	int ret;

	timer_func_in();

	while (1) {
		pthread_mutex_lock(&ptt->tt_lock);

		/*
		start_timer is called, cond-lock is released,
		but at that time,
		if stop_timer is called before wake up cond-wait, the list becomes empty.
		Thus, we use while loop instead of if.
		*/
		while (list_empty(head))
			ret = pthread_cond_wait(&ptt->tt_cond, &ptt->tt_lock);

		to = list_entry(head->next, timer_obj_t, to_list);
		list_del_init(&to->to_list);

		ptt->tt_active = to;
		ret = pthread_cond_timedwait(&ptt->tt_cond, &ptt->tt_lock, &to->to_ts);
		ptt->tt_active = NULL;
		if (ret == ETIMEDOUT && to->to_active) {
			#if defined(TIMER_ASSERT)
			time_assert(&to->to_ts);
			#endif
			callback = to->to_callback;
			data = to->to_data;
			pthread_mutex_lock(&to->to_lock);
		}
		else
			callback = NULL;
		pthread_mutex_unlock(&ptt->tt_lock);

		if (callback) {
			timer_dprintf("+timer(%p) callback\n", to);
			callback(data);
			timer_dprintf("-timer callback\n");
			pthread_mutex_unlock(&to->to_lock);
		}
	}

	timer_func_out();
	return NULL;
}

int init_timer(timer_obj_t *timer, void (*callback)(void *), void *data)
{
	pthread_timer_t *ptt = &pthread_timer;

	if (!ptt->tt_inited) {
		timer_eprintf("Timer module hasn't been initialized!!\n");
		return -1;
	}

	timer_func_in("timer=%p, callback=%p, data=%p", timer, callback, data);

	if (!timer || !callback || !data) {
		timer_eprintf("Invalid parameter\n");
		return -1;
	}

	pthread_mutex_init(&timer->to_lock, NULL);
	INIT_LIST_HEAD(&timer->to_list);
	timer->to_callback = callback;
	timer->to_data = data;
	timer->to_active = 0;
	timer->to_created = CREATED_MASK;

	timer_func_out();
	return 0;
}

int start_timer(timer_obj_t *timer, int expire_milisec)
{
	pthread_timer_t *ptt = &pthread_timer;
	struct list_head *head = &ptt->tt_head;
	timer_obj_t *pos;
	struct timespec ts;
	int ret = 0, inserted = 0;

	if (!ptt->tt_inited) {
		timer_eprintf("Timer module hasn't been initialized!!\n");
		return -1;
	}

	timer_func_in("timer=%p, expire_milisec=%d", timer, expire_milisec);

	get_timespec_ms(&ts, expire_milisec);

	pthread_mutex_lock(&ptt->tt_lock);
	if (timer->to_created != CREATED_MASK) {
		timer_eprintf("The timer was not to_created or deleted.\n");
		goto out;
	}
	if (pthread_self() != ptt->tt_thread)
		pthread_mutex_lock(&timer->to_lock);

	memcpy(&timer->to_ts, &ts, sizeof(ts));
	timer->to_active = 1;
	timer->to_caller = __builtin_return_address(0);

	if (list_empty(head))
		list_add(&timer->to_list, head);
	else {
		list_for_each_entry(pos, head, to_list) {
			if (pos != timer) {
				if (cmp_earlier_timespec(&timer->to_ts, &pos->to_ts) == &timer->to_ts) {
					if (list_empty(&timer->to_list))	/*It isn't in list*/
						list_add(&timer->to_list, &pos->to_list);
					else
						list_move(&timer->to_list, &pos->to_list);
					inserted = 1;
					break;
				}
			}
		}

		if (!inserted) {
			if (list_empty(&timer->to_list))	/*It isn't in list*/
				list_add_tail(&timer->to_list, head);
			else
				list_move_tail(&timer->to_list, head);
		}
	}
	
	assert(!list_empty(&timer->to_list));

	if (ptt->tt_active) {
		if (ptt->tt_active == timer)
			pthread_cond_signal(&ptt->tt_cond);
		else if (list_entry(head->next, timer_obj_t, to_list) == timer) {/*is head?*/
			if (cmp_earlier_timespec(&timer->to_ts, &ptt->tt_active->to_ts)
				== &timer->to_ts) {
				list_add(&ptt->tt_active->to_list, &timer->to_list);
				pthread_cond_signal(&ptt->tt_cond);
			}
		}
	}
	else if (list_entry(head->next, timer_obj_t, to_list) == timer) /*is head?*/
		pthread_cond_signal(&ptt->tt_cond);

	if (pthread_self() != ptt->tt_thread)
		pthread_mutex_unlock(&timer->to_lock);
out:
	pthread_mutex_unlock(&ptt->tt_lock);
	timer_func_out("ret=%d", ret);
	return ret;
}

int stop_timer(timer_obj_t *timer)
{
	pthread_timer_t *ptt = &pthread_timer;
	int ret = 0;

	if (!ptt->tt_inited) {
		timer_eprintf("Timer module hasn't been initialized!!\n");
		return -1;
	}

	timer_func_in("timer=%p", timer);

	pthread_mutex_lock(&ptt->tt_lock);
	if (timer->to_created != CREATED_MASK) {
		timer_iprintf("The timer was not to_created or deleted.\n");
		goto out;
	}
	if (pthread_self() != ptt->tt_thread)
		pthread_mutex_lock(&timer->to_lock);

	timer->to_active = 0;

	if (ptt->tt_active == timer)
		pthread_cond_signal(&ptt->tt_cond);
	else
		list_del_init(&timer->to_list);

	if (pthread_self() != ptt->tt_thread)
		pthread_mutex_unlock(&timer->to_lock);
out:
	pthread_mutex_unlock(&ptt->tt_lock);
	timer_func_out("ret=%d", ret);
	return ret;
}

int del_timer(timer_obj_t *timer)
{
	int ret = 0;

	timer_func_in("timer=%p", timer);

	ret = stop_timer(timer);
	timer->to_created = 0;

	timer_func_out("ret=%d", ret);
	return ret;
}

int timer_module_init(void)
{
	pthread_timer_t *ptt = &pthread_timer;

	timer_func_in();

	INIT_LIST_HEAD(&ptt->tt_head);

	pthread_mutex_init(&ptt->tt_lock, NULL);
	pthread_cond_init(&ptt->tt_cond, NULL);
	ptt->tt_inited = 1;

	pthread_create(&ptt->tt_thread, NULL, timer_thread, (void *) NULL);
	
	timer_func_out();
	return 0;
}

void timer_module_deinit(void)
{
	pthread_timer_t *ptt = &pthread_timer;
	pthread_t thread;

	timer_func_in();
	
	if ((thread = ptt->tt_thread)) {
		ptt->tt_thread = (pthread_t) NULL;
		pthread_cancel(thread);
		pthread_join(thread, NULL);
	}

	pthread_mutex_destroy(&ptt->tt_lock);
	pthread_cond_destroy(&ptt->tt_cond);
	ptt->tt_inited = 0;

	timer_func_out();
}
#else
#include <signal.h>

#define SIGNO_TIMER			SIGRTMIN

static void timer_callback(int signum, siginfo_t *si, void *sv)
{
	timer_obj_t *timer = si->_sifields._rt.si_sigval.sival_ptr;
	void (*callback)(void *data) = timer->to_callback;

	assert(signum == SIGNO_TIMER);

	timer_func_in();
	if (callback) {
		timer_dprintf("+timer callback\n");
		callback(timer->to_data);
		timer_dprintf("-timer callback\n");
	}
	timer_func_out();
}

int init_timer(timer_obj_t *timer, void (*callback)(void *), void *data)
{
	struct sigaction sa_rt;
	struct sigevent sigev;

	timer_func_in("timer=%p, callback=%p, data=%p\n", timer, callback, data);

	if (!timer || !callback || !data) {
		timer_eprintf("Invalid parameter\n");
		return -1;
	}

	memset(&sa_rt, 0, sizeof(sa_rt));
	sigemptyset(&sa_rt.sa_mask);
	sa_rt.sa_sigaction = timer_callback;
	sa_rt.sa_flags = SA_SIGINFO | SA_RESTART;

	if (sigaction(SIGNO_TIMER, &sa_rt, NULL) == -1) {
		timer_std_eprintf("sigaction\n");
		return -1;
	}

	timer->to_callback = callback;
	timer->to_data = data;
	memset(&sigev, 0, sizeof(sigev));
	sigev.sigev_value.sival_ptr = timer;
	sigev.sigev_notify = SIGEV_SIGNAL; /* notification with signal */
	sigev.sigev_signo = SIGNO_TIMER;

	/* create timer */
	if (timer_create(CLOCK_REALTIME, &sigev, (timer_t *)&timer->to_id) < 0) {
		timer_std_eprintf("timer_create\n");
		return -1;
	}

	timer->to_created = CREATED_MASK;
	timer_func_out("id=%p", timer->to_id);
	return 0;
}

int start_timer(timer_obj_t *timer, int expire_milisec)
{
	struct itimerspec rt_itspec;
	int second, nano_sec;
	int ret = 0;

	timer_func_in("timer=%p, expire_milisec=%d", timer, expire_milisec);

	if (timer->to_created != CREATED_MASK) {
		timer_iprintf("The timer was not to_created or deleted.\n");
		goto out;
	}

	/* interval timer setting */
	second = expire_milisec/1000;
	nano_sec = (expire_milisec%1000)*1000/*us*/*1000/*ns*/;
	rt_itspec.it_value.tv_sec = second;
	rt_itspec.it_value.tv_nsec = nano_sec;
	/*We do not repeat timer.*/
	rt_itspec.it_interval.tv_sec = 0;
	rt_itspec.it_interval.tv_nsec = 0;

	if (timer_settime((timer_t)timer->to_id, 0, &rt_itspec, NULL) < 0) {
		timer_std_eprintf("timer_settime(%p)\n", timer->to_id);
		timer_delete((timer_t)timer->to_id);
		ret = -1;
	}
out:
	timer_func_out("ret=%d", ret);
	return ret;
}

int del_timer(timer_obj_t *timer)
{
	int ret = 0;

	timer_func_in("timer=%p, id=%p", timer, timer->to_id);

	if (timer->to_created != CREATED_MASK) {
		timer_eprintf("Invalid timer parameter\n");
		ret = -1;
	}
	else if (timer->to_id) {
		timer->to_created = 0;
		ret = timer_delete((timer_t) timer->to_id);
	}

	timer_func_out("ret=%d", ret);
	return ret;
}
#endif

